package top.wshape1.shiziapi.project.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import feign.Request;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import top.wshape1.shiziapi.common.constant.BizCodeEnum;
import top.wshape1.shiziapi.common.constant.DocTypeEnum;
import top.wshape1.shiziapi.common.constant.RedisConstant;
import top.wshape1.shiziapi.common.domain.to.SummaryDocReqTo;
import top.wshape1.shiziapi.common.domain.vo.PageVo;
import top.wshape1.shiziapi.common.exception.CustomException;
import top.wshape1.shiziapi.common.utils.R;
import top.wshape1.shiziapi.project.common.constant.DocumentConstant;
import top.wshape1.shiziapi.project.domain.*;
import top.wshape1.shiziapi.project.domain.vo.*;
import top.wshape1.shiziapi.project.feign.ThirdPartyFeignService;
import top.wshape1.shiziapi.project.feign.UaaFeignService;
import top.wshape1.shiziapi.project.handler.LoginContextHandler;
import top.wshape1.shiziapi.project.service.*;
import top.wshape1.shiziapi.project.util.HotProjectUtil;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Wshape1
 * @version 1.0
 * @description
 * @since 2023-10-22
 */

@Slf4j
@Service
public class DocumentServiceImpl implements DocumentService {

    @Lazy
    @Resource
    private ProjectService projectService;

    @Resource
    private FolderService folderService;

    @Resource
    private ApiService apiService;

    @Resource
    private MarkdownService markdownService;

    @Resource
    private MarkdownContentService markdownContentService;

    @Resource
    private RtfService rtfService;

    @Resource
    private RtfContentService rtfContentService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private ThirdPartyFeignService thirdPartyFeignService;

    @Resource
    private DocVersionService docVersionService;

    @Resource
    private UaaFeignService uaaFeignService;

    @Resource
    private MindmapService mindmapService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private DocSummaryService docSummaryService;

    @Override
    public List<DocVo> docList(Long projectId, Long id) {

        // 先查询是否是热点项目
        Object o = redisTemplate.opsForHash().get(RedisConstant.PROJECT_HOT_KEY + projectId, RedisConstant.PROJECT_HOT_DOCLIST_KEY);
        if (o != null) {
            return (List<DocVo>) o;
        }

        Project project = projectService.getById(projectId);
        if (project.getIsPrivate() == 1) {
            if (id == null || !projectService.hasPermissionToEdit(projectId, id))
                throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        List<Folder> folderList = folderService.list(new LambdaQueryWrapper<Folder>().eq(Folder::getProjectId, projectId));
        List<Api> apiList = apiService.list(new LambdaQueryWrapper<Api>().eq(Api::getProjectId, projectId));
        List<Markdown> markdownList = markdownService.list(new LambdaQueryWrapper<Markdown>().eq(Markdown::getProjectId, projectId));
        List<Rtf> rtfList = rtfService.list(new LambdaQueryWrapper<Rtf>().eq(Rtf::getProjectId, projectId));
        List<Mindmap> mindmapList = mindmapService.list(new LambdaQueryWrapper<Mindmap>().eq(Mindmap::getProjectId, projectId)
                .select(Mindmap::getId, Mindmap::getParentFolderId, Mindmap::getOrder, Mindmap::getName)
                .eq(Mindmap::getDeleted, 0));

        /* 思路：
            将api md rtf 放入对应的目录内
            parent_folder_id 为空的在根目录，直接放入resultList，其余的放入对应的folderDocVo
            最后将folderDocVo放入对应的目录
            对每个目录内的文件进行排序
         */

        List<DocVo> resultList = new ArrayList<>();

        // 构建folder docVo map
        Map<Long, DocVo> folderDocVos = folderList.stream().map(folder -> new DocVo(
                folder.getId(),
                folder.getName(),
                DocumentConstant.FOLDER_TYPE,
                folder.getOrder(),
                new ArrayList<>())
        ).collect(Collectors.toMap(DocVo::getId, docVo -> docVo));

        // api文档放入对应目录
        for (Api api : apiList) {
            DocVo docVo = new DocVo(
                    api.getId(),
                    api.getName(),
                    DocumentConstant.API_TYPE,
                    api.getOrder());
            if (api.getParentFolderId() == null) {
                resultList.add(docVo);
            } else {
                folderDocVos.get(api.getParentFolderId()).getChildren().add(docVo);
            }
        }

        // md文档放入对应目录
        for (Markdown markdown : markdownList) {
            DocVo docVo = new DocVo(
                    markdown.getId(),
                    markdown.getName(),
                    DocumentConstant.MARKDOWN_TYPE,
                    markdown.getOrder());
            if (markdown.getParentFolderId() == null) {
                resultList.add(docVo);
            } else {
                folderDocVos.get(markdown.getParentFolderId()).getChildren().add(docVo);
            }
        }

        // rtf文档放入对应目录
        for (Rtf rtf : rtfList) {
            DocVo docVo = new DocVo(
                    rtf.getId(),
                    rtf.getName(),
                    DocumentConstant.RTF_TYPE,
                    rtf.getOrder());
            if (rtf.getParentFolderId() == null) {
                resultList.add(docVo);
            } else {
                folderDocVos.get(rtf.getParentFolderId()).getChildren().add(docVo);
            }
        }

        // mind map文档放入对应目录
        for (Mindmap mindmap : mindmapList) {
            DocVo docVo = new DocVo(
                    mindmap.getId(),
                    mindmap.getName(),
                    DocumentConstant.MINDMAP_TYPE,
                    mindmap.getOrder());
            if (mindmap.getParentFolderId() == null) {
                resultList.add(docVo);
            } else {
                folderDocVos.get(mindmap.getParentFolderId()).getChildren().add(docVo);
            }
        }


        Map<Long, Long> folderParentMap = folderList.stream().filter(folder -> folder.getParentFolderId() != null).collect(Collectors.toMap(Folder::getId, Folder::getParentFolderId));
        // 对folder放入对应目录
        for (Map.Entry<Long, DocVo> entry : folderDocVos.entrySet()) {
            Long parentId = folderParentMap.get(entry.getKey());
            if (parentId != null) {
                folderDocVos.get(parentId).getChildren().add(entry.getValue());
            }
        }

        // 顶层folder
        List<DocVo> levelZero = folderDocVos.values().stream().filter(docVo -> folderParentMap.get(docVo.getId()) == null).toList();

        resultList.addAll(levelZero);

        // 排序， children也要排序
        // 按order升序 顶层
        resultList.sort(Comparator.comparingInt(DocVo::getOrder));
        // 排列子层docVo
        resultList.forEach(this::sortDocVoRec);

        return resultList;
    }

    @Override
    public Long create(CreateDocVo createDocVo, Long userId) {
        // 查询该用户是否有该项目的编辑权限
        if (!projectService.hasPermissionToEdit(createDocVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }
        Long id = null;
        // 创建
        switch (createDocVo.getType().toLowerCase(Locale.ROOT)) {
            case DocumentConstant.FOLDER_TYPE -> {
                id = folderService.create(createDocVo);
            }
            case DocumentConstant.API_TYPE -> {
                id = apiService.create(createDocVo);
            }
            case DocumentConstant.MARKDOWN_TYPE -> {
                id = markdownService.create(createDocVo);
            }
            case DocumentConstant.RTF_TYPE -> {
                id = rtfService.create(createDocVo);
            }
            case DocumentConstant.MINDMAP_TYPE -> {
                id = mindmapService.create(createDocVo);
            }
        }

        // 更新热点项目缓存
        this.updateDocListCacheOfHotProject(createDocVo.getProjectId());
        return id;
    }

    @Override
    public void rename(RenameDocVo renameDocVo, Long userId) {
        // 查询该用户是否有该项目的编辑权限
        if (!projectService.hasPermissionToEdit(renameDocVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        switch (renameDocVo.getType().toLowerCase(Locale.ROOT)) {
            case DocumentConstant.FOLDER_TYPE -> {
                folderService.rename(renameDocVo.getId(), renameDocVo.getName());
            }
            case DocumentConstant.API_TYPE -> {
                apiService.rename(renameDocVo.getId(), renameDocVo.getName());
            }
            case DocumentConstant.MARKDOWN_TYPE -> {
                markdownService.rename(renameDocVo.getId(), renameDocVo.getName());
            }
            case DocumentConstant.RTF_TYPE -> {
                rtfService.rename(renameDocVo.getId(), renameDocVo.getName());
            }
            case DocumentConstant.MINDMAP_TYPE -> {
                mindmapService.rename(renameDocVo.getId(), renameDocVo.getName());
            }
        }

        // 更新热点项目缓存
        this.updateDocListCacheOfHotProject(renameDocVo.getProjectId());

    }

    @Override
    public void delete(DeleteDocVo deleteDocVo, Long userId) {
        // 查询该用户是否有该项目的编辑权限
        if (!projectService.hasPermissionToEdit(deleteDocVo.getProjectId(), userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        switch (deleteDocVo.getType().toLowerCase(Locale.ROOT)) {
            case DocumentConstant.FOLDER_TYPE -> {
                folderService.delete(deleteDocVo.getId());
            }
            case DocumentConstant.API_TYPE -> {
                apiService.delete(deleteDocVo.getId());
            }
            case DocumentConstant.MARKDOWN_TYPE -> {
                markdownService.delete(deleteDocVo.getId());
            }
            case DocumentConstant.RTF_TYPE -> {
                rtfService.delete(deleteDocVo.getId());
            }
            case DocumentConstant.MINDMAP_TYPE -> {
                mindmapService.delete(deleteDocVo.getId());
            }
        }

        // 更新热点项目缓存
        this.updateDocListCacheOfHotProject(deleteDocVo.getProjectId());

    }

    @Override
    public void reorder(ReorderDocVo reorderDocVo) {

        Long projectId = null;
        switch (reorderDocVo.getType().toLowerCase(Locale.ROOT)) {
            case DocumentConstant.FOLDER_TYPE -> {
                folderService.updateOrderAndParentFolderId(reorderDocVo.getId(), reorderDocVo.getOrder(), reorderDocVo.getParentFolderId());
                projectId = folderService.getById(reorderDocVo.getId()).getProjectId();
            }
            case DocumentConstant.API_TYPE -> {
                apiService.updateOrderAndParentFolderId(reorderDocVo.getId(), reorderDocVo.getOrder(), reorderDocVo.getParentFolderId());
                projectId = apiService.getById(reorderDocVo.getId()).getProjectId();
            }
            case DocumentConstant.MARKDOWN_TYPE -> {
                markdownService.updateOrderAndParentFolderId(reorderDocVo.getId(), reorderDocVo.getOrder(), reorderDocVo.getParentFolderId());
                projectId = markdownService.getById(reorderDocVo.getId()).getProjectId();
            }
            case DocumentConstant.RTF_TYPE -> {
                rtfService.updateOrderAndParentFolderId(reorderDocVo.getId(), reorderDocVo.getOrder(), reorderDocVo.getParentFolderId());
                projectId = rtfService.getById(reorderDocVo.getId()).getProjectId();
            }
            case DocumentConstant.MINDMAP_TYPE -> {
                mindmapService.updateOrderAndParentFolderId(reorderDocVo.getId(), reorderDocVo.getOrder(), reorderDocVo.getParentFolderId());
                projectId = mindmapService.getById(reorderDocVo.getId()).getProjectId();
            }
        }
        // 更新热点项目缓存

        this.updateDocListCacheOfHotProject(projectId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void reorderBatch(List<ReorderDocVo> reorderDocVos) {
        for (ReorderDocVo reorderDocVo : reorderDocVos) {
            switch (reorderDocVo.getType().toLowerCase(Locale.ROOT)) {
                case DocumentConstant.FOLDER_TYPE -> {
                    folderService.updateOrder(reorderDocVo.getId(), reorderDocVo.getOrder());
                }
                case DocumentConstant.API_TYPE -> {
                    apiService.updateOrder(reorderDocVo.getId(), reorderDocVo.getOrder());
                }
                case DocumentConstant.MARKDOWN_TYPE -> {
                    markdownService.updateOrder(reorderDocVo.getId(), reorderDocVo.getOrder());
                }
                case DocumentConstant.RTF_TYPE -> {
                    rtfService.updateOrder(reorderDocVo.getId(), reorderDocVo.getOrder());
                }
                case DocumentConstant.MINDMAP_TYPE -> {
                    mindmapService.updateOrder(reorderDocVo.getId(), reorderDocVo.getOrder());
                }
            }
        }
        // 更新热点项目缓存
        Long projectId = null;
        if (!reorderDocVos.isEmpty())
            switch (reorderDocVos.get(0).getType().toLowerCase(Locale.ROOT)) {
                case DocumentConstant.FOLDER_TYPE ->
                        projectId = folderService.getById(reorderDocVos.get(0).getId()).getProjectId();
                case DocumentConstant.API_TYPE ->
                        projectId = apiService.getById(reorderDocVos.get(0).getId()).getProjectId();
                case DocumentConstant.MARKDOWN_TYPE ->
                        projectId = markdownService.getById(reorderDocVos.get(0).getId()).getProjectId();
                case DocumentConstant.RTF_TYPE ->
                        projectId = rtfService.getById(reorderDocVos.get(0).getId()).getProjectId();
                case DocumentConstant.MINDMAP_TYPE ->
                        projectId = mindmapService.getById(reorderDocVos.get(0).getId()).getProjectId();
            }
        this.updateDocListCacheOfHotProject(projectId);
    }

    @Transactional
    @Override
    public void clearProject(long projectId) {
        apiService.clearProject(projectId);
        markdownService.clearProject(projectId);
        rtfService.clearProject(projectId);
        folderService.clearProject(projectId);
        mindmapService.clearProject(projectId);
    }

    @Override
    public UploadImageRespVO uploadImage(MultipartFile image) {
        try {
            String[] split = Objects.requireNonNull(image.getOriginalFilename()).split("\\.");
            String suffix = split[split.length - 1];
            R<Void> r = thirdPartyFeignService.uploadImageByBytes(suffix, image.getBytes());
            if (r.isSuccessful())
                return new UploadImageRespVO(split[0], r.getMsg());
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        } catch (IOException e) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }
    }

    @Override
    public DocVersionVo queryDocVersion(Long projectId, Long docId, String version, Integer docType) {

        if (Objects.isNull(projectId) || Objects.isNull(docId) || !StringUtils.hasText(version) || Objects.isNull(docType)) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }

        DocVersion one = docVersionService.getOne(new LambdaQueryWrapper<DocVersion>()
                .eq(DocVersion::getProjectId, projectId)
                .eq(DocVersion::getDocId, docId)
                .eq(DocVersion::getVersion, version)
                .eq(DocVersion::getDocType, docType)
                .eq(DocVersion::getDeleted, 0)
                .select(DocVersion::getId, DocVersion::getProjectId,
                        DocVersion::getEditorId, DocVersion::getDocId,
                        DocVersion::getDocName, DocVersion::getVersion,
                        DocVersion::getDocType, DocVersion::getSaveTime,
                        DocVersion::getUpdateTime, DocVersion::getRemark)
        );
        if (Objects.isNull(one))
            return null;

        DocVersionVo ret = new DocVersionVo();

        BeanUtils.copyProperties(one, ret);

        R<String> r = uaaFeignService.getUserNameById(one.getEditorId());
        if (r.isSuccessful())
            ret.setEditorName(r.getData());

        return ret;
    }

    @Override
    public Long saveDocVersion(DocVersionSaveVo docVersionSaveVo) {

        Long projectId = docVersionSaveVo.getProjectId();
        Long docId = docVersionSaveVo.getDocId();
        String version = docVersionSaveVo.getVersion();
        Integer docType = docVersionSaveVo.getDocType();

        if (Objects.isNull(projectId) || Objects.isNull(docId) || !StringUtils.hasText(version) || Objects.isNull(docType)) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }
        Object editObj = redisTemplate.opsForValue().get("startEdit:" + projectId + ":" + DocumentConstant.typeIntToString(docType) + ":" + docId);
        if (editObj == null || !editObj.toString().equals(String.valueOf(LoginContextHandler.getUserId()))) {
            throw new CustomException(R.instance(BizCodeEnum.DOC_NOT_IN_EDIT_ERROR));
        }

        if (!projectService.hasPermissionToEdit(projectId, LoginContextHandler.getUserId())) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        if (docVersionService.count(new LambdaQueryWrapper<DocVersion>()
                .eq(DocVersion::getProjectId, projectId)
                .eq(DocVersion::getDocId, docId)
                .eq(DocVersion::getVersion, version)
                .eq(DocVersion::getDocType, docType)
                .eq(DocVersion::getDeleted, 0)
        ) > 0) {
            // 已存在该版本
            throw new CustomException(R.instance(BizCodeEnum.DOC_VERSION_EXIST_ERROR));
        }

        //  多人同时编辑的后要注意并发问题: 限制只有一个人在编辑

        DocVersion docVersion = new DocVersion();
        docVersion.setDocId(docId);
        docVersion.setDocType(docType);
        docVersion.setProjectId(projectId);
        docVersion.setVersion(version);
        docVersion.setRemark(docVersionSaveVo.getRemark());
        docVersion.setEditorId(LoginContextHandler.getUserId());

        Date date = new Date();
        docVersion.setSaveTime(date);
        docVersion.setUpdateTime(date);

        // docName content ？？可能需要优化
        switch (docType) {
            case DocumentConstant.INT_API_TYPE -> {
                Api one = apiService.getOne(new LambdaQueryWrapper<Api>()
                        .select(Api::getName)
                        .eq(Api::getId, docId));
                docVersion.setDocName(one.getName());
                TwoIdVo twoIdVo = new TwoIdVo(docId, projectId);
                ApiDataVo apiData = apiService.getApiData(twoIdVo, LoginContextHandler.getUserId());
                List<ApiTag> apiTags = apiService.listTag(twoIdVo, LoginContextHandler.getUserId());
                HashMap<String, Object> map = new HashMap<>(2);
                map.put("apiData", apiData);
                map.put("tags", apiTags);
                docVersion.setContent(JSON.toJSONString(map));
            }
            case DocumentConstant.INT_MARKDOWN_TYPE -> {
                Markdown markdown = markdownService.getById(docId);
                docVersion.setDocName(markdown.getName());
                MarkdownContent markdownContent = markdownContentService.getById(docId);
                docVersion.setContent(markdownContent.getContent());
            }
            case DocumentConstant.INT_RTF_TYPE -> {
                Rtf rtf = rtfService.getById(docId);
                docVersion.setDocName(rtf.getName());
                RtfContent rtfContent = rtfContentService.getById(docId);
                docVersion.setContent(rtfContent.getContent());
            }
            case DocumentConstant.INT_MINDMAP_TYPE -> {
                Mindmap mindmap = mindmapService.getById(docId);
                docVersion.setDocName(mindmap.getName());
                docVersion.setContent(mindmap.getContent());
            }
        }

        docVersionService.save(docVersion);

        return docVersion.getId();
    }

    @Override
    public PageVo listPageDocVersion(DocVersionQueryVo docVersionQueryVo, Long page, Long limit) {

        Long projectId = docVersionQueryVo.getProjectId();
        Long docId = docVersionQueryVo.getDocId();
//        String version = docVersionQueryVo.getVersion();
        Integer docType = docVersionQueryVo.getDocType();

        if (Objects.isNull(projectId) || Objects.isNull(docId) || Objects.isNull(docType)) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }


        Page<DocVersion> docVersionPage = docVersionService.page(PageDTO.of(page, limit), new LambdaQueryWrapper<DocVersion>()
                .eq(DocVersion::getProjectId, projectId)
                .eq(DocVersion::getDocId, docId)
                .eq(DocVersion::getDocType, docType)
                .eq(DocVersion::getDeleted, 0)
                .orderByDesc(DocVersion::getSaveTime)
                .select(DocVersion::getId, DocVersion::getVersion,
                        DocVersion::getDocName, DocVersion::getSaveTime,
                        DocVersion::getUpdateTime, DocVersion::getRemark, DocVersion::getEditorId)
        );

        Page<DocVersionVo> ret = new Page<>();
        BeanUtils.copyProperties(docVersionPage, ret, "records");

        ret.setRecords(docVersionPage.getRecords().stream().map(docVersion -> {
            DocVersionVo docVersionVo = new DocVersionVo();
            BeanUtils.copyProperties(docVersion, docVersionVo);
            R<String> r = uaaFeignService.getUserNameById(docVersion.getEditorId());
            if (r.isSuccessful())
                docVersionVo.setEditorName(r.getData());
            return docVersionVo;
        }).collect(Collectors.toList()));

        return new PageVo(ret);
    }

    @Override
    public DocVersionVo getDocVersionWithContent(Long projectId, Long docId, String version, Integer docType) {

        DocVersionVo docVersionVo = this.queryDocVersion(projectId, docId, version, docType);
        if (docVersionVo == null)
            throw new CustomException(R.instance(BizCodeEnum.DOC_NOT_EXIST_ERROR));

        // 获取内容
        String content = this.docVersionService.getOne(new LambdaQueryWrapper<DocVersion>()
                .eq(DocVersion::getId, docVersionVo.getId())
                .eq(DocVersion::getDeleted, 0)
                .select(DocVersion::getContent)).getContent();

        docVersionVo.setContent(content);

        return docVersionVo;
    }

    @Override
    public boolean deleteDocVersion(Long id, Long projectId) {

        if (Objects.isNull(id)) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }
        if (!projectService.hasPermissionToEdit(projectId, LoginContextHandler.getUserId())) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        return this.docVersionService.update(new LambdaUpdateWrapper<DocVersion>()
                .set(DocVersion::getDeleted, 1)
                .eq(DocVersion::getId, id));
    }

    @Override
    public Object getDocument(Long docId, Integer docType) {
        return switch (docType) {
            case DocumentConstant.INT_MARKDOWN_TYPE -> markdownService.getById(docId);
            case DocumentConstant.INT_RTF_TYPE -> rtfService.getById(docId);
            case DocumentConstant.INT_API_TYPE -> apiService.getById(docId);
            case DocumentConstant.INT_FOLDER_TYPE -> folderService.getById(docId);
            case DocumentConstant.INT_MINDMAP_TYPE -> mindmapService.getById(docId);
            default -> null;

        };
    }

    @Override
    public String getDocumentName(Long docId, Integer docType) {
        return switch (docType) {
            case DocumentConstant.INT_MARKDOWN_TYPE -> markdownService.getById(docId).getName();
            case DocumentConstant.INT_RTF_TYPE -> rtfService.getById(docId).getName();
            case DocumentConstant.INT_API_TYPE -> apiService.getById(docId).getName();
            case DocumentConstant.INT_FOLDER_TYPE -> folderService.getById(docId).getName();
            case DocumentConstant.INT_MINDMAP_TYPE -> mindmapService.getById(docId).getName();
            default -> null;
        };
    }

    @Override
    public Long startEdit(StartEditVo startEditVo) {
        int typeInt = DocumentConstant.typeStringToInt(startEditVo.getDocType());
        if (typeInt == -1 || startEditVo.getProjectId() == null || startEditVo.getDocId() == null) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }

        if (!projectService.hasPermissionToEdit(startEditVo.getProjectId(), LoginContextHandler.getUserId())) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        if (this.getDocument(startEditVo.getDocId(), typeInt) == null) {
            throw new CustomException(R.instance(BizCodeEnum.DOC_NOT_EXIST_ERROR));
        }
        String key = "startEdit:" + startEditVo.getProjectId() + ":" + startEditVo.getDocType() + ":" + startEditVo.getDocId();
        DefaultRedisScript<Object> script = new DefaultRedisScript<>();
        script.setResultType(Object.class);
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/startEdit.lua")));
        Object execute = redisTemplate.execute(script, Collections.singletonList(key), LoginContextHandler.getUserId());
        return execute instanceof Integer ? Long.valueOf((Integer) execute) : (Long) execute;
    }

    @Override
    public void stopEdit(StartEditVo startEditVo) {
        int typeInt = DocumentConstant.typeStringToInt(startEditVo.getDocType());
        if (typeInt == -1 || startEditVo.getProjectId() == null || startEditVo.getDocId() == null) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }

        if (this.getDocument(startEditVo.getDocId(), typeInt) == null) {
            throw new CustomException(R.instance(BizCodeEnum.DOC_NOT_EXIST_ERROR));
        }

        String key = "startEdit:" + startEditVo.getProjectId() + ":" + startEditVo.getDocType() + ":" + startEditVo.getDocId();

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setResultType(Long.class);
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/stopEdit.lua")));
        redisTemplate.execute(script, Collections.singletonList(key), LoginContextHandler.getUserId());
    }

    @Override
    public void summaryDocument(Long projectId, Long docId, String docType) {

        DocTypeEnum docTypeEnum;
        Project project;
        String aiSummaryKey = RedisConstant.DOC_AI_SUMMARY_KEY + projectId + ":" + docId + ":" + docType;
        String aiErrorKey = RedisConstant.AI_SUMMARY_ERROR_KEY + projectId + ":" + docId + ":" + docType;

        if (projectId == null || docId == null || (docTypeEnum = DocTypeEnum.getDocTypeEnum(docType)) == null) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }

        // 判断是否正在生成summary
        if (Boolean.TRUE.equals(redisTemplate.hasKey(aiSummaryKey))) {
            throw new CustomException(R.instance(BizCodeEnum.DOC_AI_SUMMARY_GENERATING));
        }

        // 上一次生成summary失败
        Object aiErrorObj;
        if ((aiErrorObj = redisTemplate.opsForValue().getAndDelete(aiErrorKey)) != null) {
            throw new CustomException(R.instance(Objects.requireNonNull(BizCodeEnum.valueOfByCode((int) aiErrorObj))));
        }

        if ((project = projectService.getById(projectId)) == null) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_NOT_EXIST_ERROR));
        }

        long userId = LoginContextHandler.getUserId();
        if (!projectService.hasPermissionToEdit(projectId, userId)) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        CompletableFuture.runAsync(() -> {
            RLock lock = redissonClient.getLock(aiSummaryKey);
            log.info("start summary document");
            try {
                if (lock.tryLock()) {
                    SummaryDocReqTo reqTo = new SummaryDocReqTo();
                    switch (docTypeEnum) {
                        case API:
                            Api api = apiService.getById(docId);
                            ApiDataVo apiData = apiService.getApiData(new TwoIdVo(docId, projectId), userId);
                            reqTo.setDocName(api.getName());
                            reqTo.setDocContent(JSON.toJSONString(apiData.getContents()));
                            break;
                        case MARKDOWN:
                            Markdown markdown = markdownService.getById(docId);
                            String markdownContent = markdownService.getContent(new TwoIdVo(docId, projectId), userId);
                            reqTo.setDocName(markdown.getName());
                            reqTo.setDocContent(markdownContent);
                            break;
                        case RTF:
                            Rtf rtf = rtfService.getById(docId);
                            String rtfContent = rtfService.getContent(new TwoIdVo(docId, projectId), userId);
                            reqTo.setDocName(rtf.getName());
                            reqTo.setDocContent(rtfContent);
                            break;
                        case MIND_MAP:
                            Mindmap mindmap = mindmapService.getById(docId);
                            reqTo.setDocName(mindmap.getName());
                            reqTo.setDocContent(mindmap.getContent());
                            break;
                    }

                    reqTo.setProjectName(project.getName());
                    reqTo.setDocType(docTypeEnum);

                    R<String> r = thirdPartyFeignService.summaryDoc(new Request.Options(3000L, TimeUnit.MILLISECONDS,
                            180L, TimeUnit.SECONDS, true), reqTo);
                    if (!r.isSuccessful()) {
                        log.warn("调用第三方thirdPartyFeignService.summaryDoc(reqTo)失败:{}", r);
                        // TODO r返回的信息可能为AI服务的错误信息，如内容涉嫌违规，非本系统错误信息，需要做判断，不一样的AI服务商错误信息不一样
                        throw new CustomException(R.instance(BizCodeEnum.SYSTEM_BUSY));
                    }

                    // 保存至数据库
                    // TODO 可能使用事物保证原子性，不用也没关系，查询时查询最新数据即可
                    docSummaryService.update(new LambdaUpdateWrapper<DocSummary>()
                            .eq(DocSummary::getProjectId, projectId)
                            .eq(DocSummary::getDocId, docId)
                            .eq(DocSummary::getDocType, docType)
                            .eq(DocSummary::getDeleted, 0)
                            .set(DocSummary::getDeleted, 1));
                    DocSummary docSummary = new DocSummary();
                    docSummary.setProjectId(projectId);
                    docSummary.setDocId(docId);
                    docSummary.setDocType(docType);
                    docSummary.setSummary(r.getData());
                    docSummary.setCreateTime(new Date());
                    docSummary.setCreatorId(userId);
                    docSummaryService.save(docSummary);
                    log.info("保存文档summary成功");

                } else {
                    throw new CustomException(R.instance(BizCodeEnum.DOC_AI_SUMMARY_GENERATING));
                }
            } catch (Exception e) {
                log.error("summaryDocument error:{}", e.getMessage());

                // 缓存错误信息
                redisTemplate.opsForValue().set(aiErrorKey,
                        BizCodeEnum.SYSTEM_BUSY.code(), 10, TimeUnit.SECONDS);

                throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
            } finally {
                lock.unlock();
            }
        }, threadPoolExecutor);

    }

    @Override
    public DocSummaryVo getSummaryDocument(Long projectId, Long docId, String docType) {
        String aiSummaryKey = RedisConstant.DOC_AI_SUMMARY_KEY + projectId + ":" + docId + ":" + docType;

        if (projectId == null || docId == null || DocTypeEnum.getDocTypeEnum(docType) == null) {
            throw new CustomException(R.instance(BizCodeEnum.UNKNOWN_ERROR));
        }

        if (projectService.count(new LambdaQueryWrapper<Project>().eq(Project::getId, projectId)) < 1) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_NOT_EXIST_ERROR));
        }

        if (!projectService.hasPermissionToEdit(projectId, LoginContextHandler.getUserId())) {
            throw new CustomException(R.instance(BizCodeEnum.PROJECT_PERMISSION_ERROR));
        }

        DocSummaryVo summaryVo = new DocSummaryVo();

        // 判断是否正在生成summary
        summaryVo.setGenerating(Boolean.TRUE.equals(redisTemplate.hasKey(aiSummaryKey)));

        DocSummary summary = docSummaryService.getOne(new LambdaQueryWrapper<DocSummary>()
                .eq(DocSummary::getProjectId, projectId)
                .eq(DocSummary::getDocId, docId)
                .eq(DocSummary::getDocType, docType)
                .eq(DocSummary::getDeleted, 0)
                .orderByDesc(DocSummary::getCreateTime)
                .last(" limit 1")
        );

        if (summary != null) {
            summaryVo.setCreatorId(summary.getCreatorId());
            summaryVo.setCreateTime(summary.getCreateTime());
            summaryVo.setSummary(summary.getSummary());
        }

        return summaryVo;
    }

    @Override
    public DocSummaryGeneratingVo isSummaryGenerating(Long projectId, Long docId, String docType) {

        Object aiErrorObj = redisTemplate.opsForValue().getAndDelete(RedisConstant.AI_SUMMARY_ERROR_KEY + projectId + ":" + docId + ":" + docType);
        if (aiErrorObj != null) {
            throw new CustomException(R.instance(Objects.requireNonNull(BizCodeEnum.valueOfByCode((int) aiErrorObj))));
        }

        String aiSummaryKey = RedisConstant.DOC_AI_SUMMARY_KEY + projectId + ":" + docId + ":" + docType;

        DocSummaryGeneratingVo vo = new DocSummaryGeneratingVo();

        vo.setGenerating(Boolean.TRUE.equals(redisTemplate.hasKey(aiSummaryKey)));

        return vo;
    }

    /**
     * 递归排序子层文件
     *
     * @param docVo
     */
    private void sortDocVoRec(DocVo docVo) {
        if (docVo.getChildren() == null || docVo.getChildren().isEmpty())
            return;

        docVo.getChildren().sort(Comparator.comparingInt(DocVo::getOrder));

        for (int i = 0; i < docVo.getChildren().size(); i++) {
            sortDocVoRec(docVo.getChildren().get(i));
        }
    }

    private void updateDocListCacheOfHotProject(Long projectId) {
        if (projectId == null)
            return;
        List<DocVo> docVos = this.docList(projectId, LoginContextHandler.getUserId());
        HotProjectUtil.updateCacheIfHot(redisTemplate,
                projectId, RedisConstant.PROJECT_HOT_DOCLIST_KEY, docVos);
    }

}
